home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue35 / tobject / delphi4.txt next >
Encoding:
Text File  |  1998-06-18  |  5.7 KB  |  212 lines

  1. The Rise And Fall of TObject - update for Delphi 4:
  2.  
  3. The new Delphi 4 version changes things a bit when it comes to how
  4. constructors and destructors work.
  5.  
  6.  
  7. NEW VIRTUAL METHODS
  8. There are two new virtual methods defined in TObject that are
  9. called during construction and destruction:
  10.  
  11. TObject = class
  12.   ..
  13.   procedure AfterConstruction; virtual;
  14.   procedure BeforeDestruction; virtual;
  15.   ..
  16. end;
  17.  
  18. By default these methods do nothing and are implemeted in TObject as:
  19.  
  20. procedure TObject.AfterConstruction;
  21. begin
  22. end;
  23.  
  24. procedure TObject.BeforeDestruction;
  25. begin
  26. end;
  27.  
  28. These are normal methods, so no special code is generated here.
  29.  
  30. The new code generatead for the constructor, now looks like this:
  31.  
  32. class function TMyObject.Create(SelfClass: TClass; CalledWithClass: boolean): TMyObject;
  33. var
  34.   Self: TMyObject;
  35. begin
  36.   if CalledWithClass
  37.   then Self := _ClassCreate(SelfClass)
  38.   else Self := TObject(SelfClass);
  39.  
  40.   // Actual constructor code goes here
  41.  
  42.   if CalledWithClass then
  43.   begin
  44.     // Remove exception frame
  45.     _AfterConstruction(Self);
  46.   end;
  47.   Result := Self;
  48. end;
  49.  
  50. _AfterConstruction is a new magic function defined in the System unit.
  51. It's pseudo-code is something like this:
  52.  
  53. procedure _AfterConstruction(Instance: TObject);
  54. begin
  55.   Instance.AfterConstruction;
  56. end;
  57.  
  58. Also the generated code for the destructors has been changed
  59. to handle the new BeforeDestruction method:
  60.  
  61. procedure TObject.Destroy(Self: TObject; Deallocate: boolean);
  62. begin
  63.   if Deallocate then
  64.     _BeforeDestruction(Self, Deallocate);
  65.   // Actual destructor code goes here
  66.   if Deallocate then
  67.     _ClassDestroy(Self);
  68. end;
  69.  
  70. _BeforeDestruction is another new magic function defined in the System unit.
  71. It's pseudo-code is something like this:
  72.  
  73. procedure _BeforeDestruction(Instance: TObject; Deallocate: boolean);
  74. begin
  75.   if Deallocate then
  76.    Instance.BeforeDestruction;
  77. end;
  78.  
  79. The introduction of the AfterConstruction and BeforeDestruction methods allows us
  80. to hook into the construction chain at yet another level. The total picture of
  81. the construction process thus looks like this:
  82.  
  83. constructor
  84.     _CreateClass
  85.         NewInstance
  86.             GetMem
  87.             InitInstance
  88.   _AfterConstruction
  89.     AfterConstruction
  90.  
  91. destructor
  92.     _DestroyClass
  93.         FreeInstance
  94.             CleanupInstance
  95.             FreeMem
  96.   _BeforeDestruction
  97.     BeforeDestruction
  98.  
  99. Starts to look a little complicated doesn't it?
  100.  
  101. The reason for introducing these new virutal methods seems
  102. to be compability with the C++Builder version of the VCL and
  103. compiler. If you look closely at the VCL source code supplied
  104. with Delphi 4 Pro and up, you will find that these methods
  105. are only overriden in the TCustomForm and TDataModule classes.
  106.  
  107.  
  108. CHANGED CONSTRUCTOR SEMANTICS
  109. Another issue to help compability with BCB seems to be the
  110. value used for the implicit CalledWithClass parameter of
  111. constructors. In previous versions of Delphi, the parameter
  112. (contained in the DL register) would always be 1=true or 0=false.
  113.  
  114. In Delphi 4, this parameter can also be -1 (or $FF when treated
  115. as a byte). The DL parameter will be -1 when calling a constructor
  116. through an existing object instance from outside a constructor.
  117.  
  118. That sentence was a bit complicated so lets give a couple of examples:
  119.  
  120. Ok, calling a constructor through a class reference, always sets the DL
  121. parameter to 1, no matter where it is called from:
  122.  
  123.   MyObject := TMyObject.Create;
  124.  
  125. is compiled into:
  126.  
  127.   MyObject := TMyObject.Create(TMyObject, DL=1);
  128.  
  129. This is the same as before. No change here.
  130. When calling a constructor through an existing object reference, things
  131. will work as before as long as you are calling the constructor from
  132. within another constructor:
  133.  
  134. constructor TMyOtherObject.Create;
  135. begin
  136.   inherited Create;
  137.   ...
  138. end;
  139.  
  140. constructor TMyOtherObject.Create2;
  141. begin
  142.   Self.Create;
  143.   ...
  144. end;
  145.  
  146. This will compile into (we only consider the calls here):
  147.  
  148. constructor TMyOtherObject.Create;
  149. begin
  150.   inherited Create(Self, DL=0);
  151.   ...
  152. end;
  153.  
  154. constructor TMyOtherObject.Create2;
  155. begin
  156.   Self.Create(Self, DL=0);
  157.   ...
  158. end;
  159.  
  160. So far so good. Things still work exactly has they have before.
  161. Now to the culprit. If we move the call to the constructor
  162. outside the constructor, DL will be set to -1 instead of 0.
  163.  
  164. For instance:
  165.  
  166. procedure TMyOtherObject.Init;
  167. begin
  168.   Self.Create;
  169.   ...
  170. end;
  171.  
  172. This will compile into:
  173.  
  174. procedure TMyOtherObject.Init;
  175. begin
  176.   Self.Create(Self, DL=-1);;
  177.   ...
  178. end;
  179.  
  180. So now the second implied parameter to the constructor can have
  181. three values 0, 1 or -1. Here is how the constructor behaves in each case:
  182.  
  183. Case 1 (DL=1):
  184.      a) Called on class reference
  185.      b) Setup an exception frame to automatically call free in case of problems
  186.      c) Allocate and initialize memory for the instance
  187.  
  188. Case 2 (DL=0):
  189.      a) Called on existing instance from another constructor
  190.      b) Do not setup any exception frame
  191.      c) Do not allocate memory for the instance
  192.  
  193. Case 3 (DL=-1):
  194.      a) Called on existing instance from non-constructor code
  195.      b) Setup an exception frame to automatically call free in case of problems
  196.      c) Do not allocate memory for the instance
  197.  
  198. Case 1 and 2 works like before. Case 3 is the new situation and it is point 3b) that
  199. can potentially case problems. This is a change in the semantics of constructors.
  200.  
  201. The problem is that earlier, calling a constructor on an existing object instance,
  202. the constructor would work just like a method call. With the new semantics,
  203. the instance will be freed if the constructor raises an exception.
  204.  
  205. So what should you do to avoid this problem? As far as possible, refrain from using
  206. the construct:
  207.  
  208.   Instance.Create;
  209.  
  210. from other places than inside other constructors.
  211.  
  212.